#ifndef _TEMPLE_UTIL_PINGER_H
#define _TEMPLE_UTIL_PINGER_H

#include "util/stdc.h"
#include "util/misc.h"
namespace util {

#ifndef __LITTLE_ENDIAN
#define __LITTLE_ENDIAN	0
#endif

#ifndef __BIG_ENDIAN
#define __BIG_ENDIAN	1
#endif

#ifndef __BYTE_ORDER
#define __BYTE_ORDER __LITTLE_ENDIAN
#endif

#pragma pack(4)

struct IPV4
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
    u32 ip_hl:4;            /* header length */
    u32 ip_v:4;             /* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
    u32 ip_v:4;             /* version */
    u32 ip_hl:4;            /* header length */
#endif
    u8 ip_tos;              /* type of service */
    u16 ip_len;             /* total length */
    u16 ip_id;              /* identification */

	#define IP_RF 0x8000    /* reserved fragment flag */
	#define IP_DF 0x4000    /* dont fragment flag */
	#define IP_MF 0x2000    /* more fragments flag */

    u16 ip_off;             /* fragment offset field */
    u8 ip_ttl;              /* time to live */
    u8 ip_p;                /* protocol */
    u16 ip_sum;             /* checksum */
    struct in_addr ip_src;  /* source address */
    struct in_addr ip_dst;  /* dest address */
};

struct ICMP
{
	#define ICMP_ECHO   	8
	#define ICMP_ECHOREPLY  0

    u8  icmp_type;          /* type of message, see below */
    u8  icmp_code;          /* type sub code, default 0 */
    u16 icmp_cksum;         /* ones complement checksum of struct */
    u16 icmp_id;
    u16 icmp_seq;
    u8  icmp_data[32];

    ICMP()
    {
        static short pack_no = 0;
        icmp_type = ICMP_ECHO;
        icmp_code = 0;
        icmp_cksum = 0;
        icmp_id = htons((short)1);
        icmp_seq = htons(++pack_no);
        memset(icmp_data, '0', sizeof(icmp_data));
        icmp_cksum = htons(calc_chksum((u8 *)this, sizeof(*this)));
    }

	u16 calc_chksum(u8 *buf, int len)
	{
        u32 sum = 0;

        while (len > 1)
        {
            sum += (((u16)buf[0])<<8) + (u16)buf[1];
            buf += 2;
            len -= 2;
	    }

        if (len == 1)
        {
            sum += ((u16)buf[0])<<8;
        }

        sum = (sum>>16) + (sum&0xffff);
        sum += (sum>>16);
        return (u16)~sum;
	}
};

class Pinger
{
public:
	Pinger()
	{
#ifdef WIN32
		WSADATA wsaData;
		WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
	}

	int ping(const char *remote, int &ttl, int &rtt)
	{
		struct protoent *protocol;
		protocol = getprotobyname("icmp");
		if (!protocol)
			return 1;

		int fd = socket(AF_INET, SOCK_RAW, protocol->p_proto);
		if(fd < 0)
        	return 2;

		int ret;

		int param = 64*1024;
        ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *)&param, sizeof(param));
        ret = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *)&param, sizeof(param));

		param = 255;
		ret = setsockopt(fd, IPPROTO_IP, IP_TTL, (char *)&param, sizeof(param));

#ifdef WIN32
		int timeout = 2000;
#else
		struct timeval timeout = {2, 0};
#endif
		ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
		ret = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout));

        sockaddr_t sa;
        socklen_t sa_len = sizeof(sa);

        struct sockaddr_in *sa_in = (struct sockaddr_in *)&sa;
        sa_in->sin_family = AF_INET;
        sa_in->sin_port = 0;
        sa_in->sin_addr.s_addr = inet_addr(remote);

        ICMP icmp;
        u64 start = util::get_cur_time();

        ret = sendto(fd, (char *)&icmp, sizeof(icmp), 0, &sa, sa_len);
        if (ret <= 0)
		{
			closesocket(fd);
        	return 3;
		}

        char buf[1024];
        ret = recvfrom(fd, buf, sizeof(buf), 0, &sa, &sa_len);
        if (ret <= 0)
		{
			closesocket(fd);
            return 4;
		}

        IPV4 *p_ipv4 = (IPV4 *)buf;
        u32 hdr_len = p_ipv4->ip_hl * 4;

        if ((u32)ret != hdr_len+sizeof(icmp))
		{
			closesocket(fd);
            return 5;
		}

        ICMP *p_icmp = (ICMP *)(buf + hdr_len);
        if (p_icmp->icmp_type != ICMP_ECHOREPLY)
		{
			closesocket(fd);
            return 6;
		}

        if (p_icmp->icmp_seq != icmp.icmp_seq)
		{
			closesocket(fd);
            return 7;
		}

        ttl = p_ipv4->ip_ttl;
        rtt = (int)(util::get_cur_time() - start);

		closesocket(fd);
        return 0;
	}
};

}
#endif //_TEMPLE_UTIL_PINGER_H
